﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Windows.Data;
using System.Windows;

using PetriNetSharp.Engine;
using PetriNetSharp.View;
using PetriNetSharp.Converter;
using System.Windows.Controls;
using PetriNetSharp.CustomUIControls;
using System.Collections.ObjectModel;
using System.Windows.Media;


namespace PetriNetSharp.Controller
{
	/// <summary>
	/// The controller class that connects the Transition model and the Tranition view
	/// </summary>
	public class TransitionController : IControllerInvalidatable
	{

		/// <summary>
		/// The model this is controlling
		/// </summary>
		private Transition model;

		/// <summary>
		/// The view this is controlling
		/// </summary>
		private TransitionView view;

		/// <summary>
		/// A converter for the location of the transition
		/// </summary>
		private PointToTransitionLocationValueConverter locationConverter = new PointToTransitionLocationValueConverter();

		/// <summary>
		/// The context menu for the view when it is being modified
		/// </summary>
		private ContextMenu modifyContextMenu;

		/// <summary>
		/// The delete menu item for the view's context menu
		/// </summary>
		private MenuItem deleteMenuItem;

		/// <summary>
		/// The menu slider for the view's tokens to transfer
		/// </summary>
		private Slider tokensToTransferSlider;

		/// <summary>
		/// The tool tip to use for the transition view
		/// </summary>
		private SharpToolTip toolTip;

		/// <summary>
		/// A converter for the tool tip's header
		/// </summary>
		private TransitionToToolTipHeaderValueConverter toolTipHeaderValueConverter = 
			new TransitionToToolTipHeaderValueConverter();

		/// <summary>
		/// A converter for use with the is enabled properties with IsRunning
		/// </summary>
		private InvertBooleanValueConverter isEnabledValueConverter =
			new InvertBooleanValueConverter();

		/// <summary>
		/// Create a new TransitionController
		/// </summary>
		/// <param name="model">The model this is controlling</param>
		/// <param name="view">The view this is controlling</param>
		/// <preconditions>
		/// view cannot be null
		/// model cannot be null
		/// </preconditions>
		public TransitionController(Transition model, TransitionView view)
		{
			#region preconditions
			Debug.Assert(view != null, "TransitionController parameter view cannot be null", "The parameter view for TransitionController cannot be null for the constructor");
			Debug.Assert(model != null, "TransitionController parameter model cannot be null", "The parameter model for TransitionController cannot be null for the constructor");
			#endregion

			toolTip = new SharpToolTip();

			modifyContextMenu = new ContextMenu();
			//Set up the tokensToTransfer slider
			tokensToTransferSlider = new Slider();
			tokensToTransferSlider.Maximum = 25;
			tokensToTransferSlider.Minimum = 1;
			tokensToTransferSlider.TickFrequency = 1;
			tokensToTransferSlider.IsSnapToTickEnabled = true;
			tokensToTransferSlider.AutoToolTipPlacement = 
				System.Windows.Controls.Primitives.AutoToolTipPlacement.BottomRight;
			SharpToolTip tokensToTransferToolTip = new SharpToolTip();
			tokensToTransferToolTip.Header = "Tokens Transfer on Fire";
			tokensToTransferToolTip.Text = "Change the number of tokens this Transition will take from " +
				"each Input Place and give to each Output Place.";
			tokensToTransferSlider.ToolTip = tokensToTransferToolTip;
			modifyContextMenu.Items.Add(tokensToTransferSlider);

			modifyContextMenu.Items.Add(new Separator());

			//Set up the delete menu item so the user can delete this transition
			deleteMenuItem = new MenuItem();
			deleteMenuItem.Header = "Delete Transition";
			deleteMenuItem.Click += delegate(object sender, RoutedEventArgs e)
				{
					Model.Invalidate();
				};
			SharpToolTip deleteToolTip = new SharpToolTip();
			deleteToolTip.Header = "Delete Transition";
			deleteToolTip.Text = "Click this to delete this transition from the Petri Net.";
			deleteMenuItem.ToolTip = deleteToolTip;
			modifyContextMenu.Items.Add(deleteMenuItem);

			

			this.view = view;
			this.model = model;

			RemoveConnections();
			SetConnections();
		}


		/// <summary>
		/// Get or set the model
		/// </summary>
		/// <preconditions>
		/// On set: model cannot be null
		/// </preconditions>
		public Transition Model
		{
			get { return model; }
			set
			{
				#region preconditions
				Debug.Assert(value != null, "Model cannot be null",
					"The TransitionController was passed a null value for its model");
				#endregion

				RemoveConnections();
				model = value;

				RemoveConnections();
				SetConnections();
			}
		}

		/// <summary>
		/// Get or set the view
		/// </summary>
		/// <preconditions>
		/// On set: value cannot be null
		/// </preconditions>
		public TransitionView View
		{
			get { return view; }
			set
			{
				#region preconditions
				Debug.Assert(value != null, "View cannot be null",
					"The TransitionsController was passed a null value for its view");
				#endregion	

				RemoveConnections();
				view = value;
				RemoveConnections();
				SetConnections();
			}
		}

		#region set and remove connections from View to Model

		/// <summary>
		/// Remove the connections between the view and model
		/// </summary>
		private void RemoveConnections()
		{
			//Remove all bindings
			if(BindingOperations.IsDataBound(View, TransitionView.TransitionNameProperty))
				BindingOperations.ClearBinding(View, TransitionView.TransitionNameProperty);
			
			if (BindingOperations.IsDataBound(View, Canvas.TopProperty))
				BindingOperations.ClearBinding(View, Canvas.TopProperty);

			if (BindingOperations.IsDataBound(View, Canvas.LeftProperty))
				BindingOperations.ClearBinding(View, Canvas.LeftProperty);

			if(BindingOperations.IsDataBound(tokensToTransferSlider, Slider.ValueProperty))
				BindingOperations.ClearBinding(tokensToTransferSlider, Slider.ValueProperty);

			//Remove the View's context menu
			View.ContextMenu = null;

			//Remove the View's tool tip
			View.ToolTip = null;

			//Remove the Model's invalidation listener
			Model.Invalidated -= Invalidate;

			//Remove Model's connected places changed listener
			Model.ConnectedPlacesChanged -= SetToolTipText;

			//Remove change listeners for each place the model is connected to
			foreach (Place place in Model.InputPlaces)
				place.NameChanged -= SetToolTipText;
			foreach (Place place in Model.OutputPlaces)
				place.NameChanged -= SetToolTipText;

			//Stop listening for places being added and removed
			Model.PlaceConnected -= AttachPlacePropertyListeners;
			model.PlaceDisconnected -= RemovePlacePropertyListeners;

			//Stop listenering for IsRunning changing
			Model.IsRunningChanged -= IsRunningChangedListener;

			//Stop listening for tokens to transfer changes
			Model.TokensToTransferChanged -= SetToolTipText;

		}

		/// <summary>
		/// Attaches the name, running, and starting token change listeners
		/// </summary>
		/// <param name="place">The place to listener to</param>
		private void AttachPlacePropertyListeners(Place place)
		{
			place.NameChanged += SetToolTipText;
			place.RunningTokensChanged += SetToolTipText;
			place.StartingTokensChanged += SetToolTipText;
		}

		/// <summary>
		/// Removes the name, running, and starting token change listeners
		/// </summary>
		/// <param name="place">The place to remove listeners</param>
		private void RemovePlacePropertyListeners(Place place)
		{
			place.NameChanged -= SetToolTipText;
			place.RunningTokensChanged -= SetToolTipText;
			place.StartingTokensChanged -= SetToolTipText;
		}


		/// <summary>
		/// Sets the connections between the view and the model
		/// </summary>
		private void SetConnections()
		{
			//Add View's transition name binding
			Binding bind = new Binding();
			bind.Source = Model;
			bind.Path = new PropertyPath("Name");
			bind.Mode = BindingMode.TwoWay;
			View.SetBinding(TransitionView.TransitionNameProperty, bind);

			//Set location bindings
			bind = new Binding();
			bind.Source = Model;
			bind.Path = new PropertyPath("Location");
			bind.Converter = locationConverter;
			bind.ConverterParameter = PointToDoubleValueConverterParams.X;
			View.SetBinding(Canvas.LeftProperty, bind);

			bind = new Binding();
			bind.Source = Model;
			bind.Path = new PropertyPath("Location");
			bind.Converter = locationConverter;
			bind.ConverterParameter = PointToDoubleValueConverterParams.Y;
			View.SetBinding(Canvas.TopProperty, bind);

			//Set tokens to transfer binding
			bind = new Binding();
			bind.Source = Model;
			bind.Path = new PropertyPath("TokensToTransfer");
			bind.Mode = BindingMode.TwoWay;
			tokensToTransferSlider.SetBinding(Slider.ValueProperty, bind);

			//Add enable bindings
			bind = new Binding();
			bind.Source = Model;
			bind.Path = new PropertyPath("IsRunning");
			bind.Converter = isEnabledValueConverter;
			View.SetBinding(TransitionView.TransitionNameIsEnabledProperty, bind);

			//Attach the context menu
			View.ContextMenu = modifyContextMenu;

			//Set up the view's tool tip
			View.ToolTip = toolTip;
			bind = new Binding();
			bind.Source = Model;
			bind.Path = new PropertyPath("Name");
			bind.Mode = BindingMode.TwoWay;
			bind.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
			bind.Converter = toolTipHeaderValueConverter;
			toolTip.SetBinding(SharpToolTip.HeaderProperty, bind);
			SetToolTipText();

			//Attach the Model's invalidated listener
			Model.Invalidated += Invalidate;

			//Attach Model's connected places changed listener
			Model.ConnectedPlacesChanged += SetToolTipText;

			//Attach change listeners for each place the model is connected to
			foreach (Place place in Model.InputPlaces)
				AttachPlacePropertyListeners(place);
			foreach (Place place in Model.OutputPlaces)
				AttachPlacePropertyListeners(place);

			//Attach listeners for places being added and removed
			Model.PlaceConnected += AttachPlacePropertyListeners;
			Model.PlaceDisconnected += RemovePlacePropertyListeners;

			//Attach listeners for IsRunning changing
			Model.IsRunningChanged += IsRunningChangedListener;

			//Attach listener for tokens to transfer changes
			Model.TokensToTransferChanged += SetToolTipText;
		}

		/// <summary>
		/// Listens for when IsRunning changes and updates the view
		/// </summary>
		private void IsRunningChangedListener()
		{
			if (Model.IsRunning)
			{
				View.ContextMenu = null;
				View.TransitionNameForeground = new SolidColorBrush(Colors.Black);
			}
			else
			{
				View.ContextMenu = modifyContextMenu;
				View.TransitionNameForeground = new SolidColorBrush(Colors.White);
			}
		}

		/// <summary>
		/// Sets the tool tip text to be in the format:
		/// Tokens Transfer on Fire: ## 
		/// Input Places:
		///		Name - # Tokens
		///		Name - # Tokens
		/// Ouput Places:
		///		Name - # Tokens
		///		Name - # Tokens
		/// </summary>
		private void SetToolTipText()
		{
			//Generate the input places
			string text = string.Format("Tokens Transfer on Fire: {0}{1}", Model.TokensToTransfer, Environment.NewLine);
			text += "Input Places:" + Environment.NewLine;
			ReadOnlyObservableCollection<Place> inputPlaces = Model.InputPlaces;
			if (inputPlaces.Count == 0)
				text += "\t None" + Environment.NewLine;
			else
			{
				foreach(Place place in inputPlaces)
					text += String.Format( "\t{0} - {1} token(s)", place.Name, place.CurrentTokens)  
						 + Environment.NewLine;
			}

			//Generate the output places
			text += "Output Places:" + Environment.NewLine;
			ReadOnlyObservableCollection<Place> outputPlaces = Model.OutputPlaces;
			if (outputPlaces.Count == 0)
				text += "\t None" + Environment.NewLine;
			else
			{
				foreach (Place place in outputPlaces)
					text += String.Format("\t{0} - {1} token(s)", place.Name, place.CurrentTokens)
						 + Environment.NewLine;
			}
			//Remove the last newline
			text = text.Remove(text.LastIndexOf(Environment.NewLine), Environment.NewLine.Length);

			toolTip.Text = text;
		}

		#endregion

		#region move functionality

		/// <summary>
		/// Whether the Transition is currently moving
		/// </summary>
		private bool isMoving;


		/// <summary>
		/// The last point to move against
		/// </summary>
		private Point lastPoint;

		/// <summary>
		/// Start the moving functionality
		/// </summary>
		/// <param name="point">The point to start the moving from</param>
		public void StartMove(Point point)
		{
			if (!Model.IsRunning)
			{
				isMoving = true;
				lastPoint = point;
				View.CaptureMouse();
			}
		}

		/// <summary>
		/// Move the Place
		/// </summary>
		/// <param name="point">The new point of the moving</param>
		public void MovePlaceView(Point point)
		{
			if (this.isMoving)
			{
				double diffX = point.X - this.lastPoint.X;
				double diffY = point.Y - this.lastPoint.Y;
				this.lastPoint = point;
				Point newPoint = new Point(Model.Location.X + diffX,
				    Model.Location.Y + diffY);
				Model.Location = newPoint;
			}
		}

		/// <summary>
		/// Ends the moving funcitonality
		/// </summary>
		public void EndMove()
		{
			isMoving = false;
			View.ReleaseMouseCapture();
		}

		#endregion

		#region IControllerInvalidatable Members

		/// <summary>
		/// Occurs when this TransitionController is no longer valid
		/// </summary>
		public event ControllerInvalidatedEventHandler Invalidated;

		/// <summary>
		/// Invalidate this TransitionController
		/// </summary>
		public void Invalidate()
		{
			if (Invalidated != null)
				Invalidated(this);
		}

		#endregion
	}
}
